// Copyright 2010, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_ATOMIC_H_
#define LIBV_ATOMIC_H_

#include <istream>
#include <ostream>

#ifdef _WIN32
# include <libv/windows_lean.h>  // можно заменить на windows.h
# include <intrin.h>
typedef __int32 int32_t;

# if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
#  pragma intrinsic(_InterlockedAnd)
#  pragma intrinsic(_InterlockedOr)
#  pragma intrinsic(_InterlockedXor)
# endif  // _MSC_VER
#else
# include <stdint.h>
#endif  // _WIN32


namespace libv
{

template<class TAtomicType> class AtomicOp;
template<class TAtomicType> class Atomic;



// Пока поддерживается только 32-битные операции под Windows и GCC
typedef volatile int32_t AtomicType32;
typedef AtomicOp<AtomicType32> AtomicOp32;
typedef Atomic<AtomicType32> Atomic32;



template<>
class AtomicOp<AtomicType32>
{
  public:
    typedef int32_t ArgType;

    /** Атомарное чтение переменной под адресу pvar. */
    inline static AtomicType32 Read(const AtomicType32* pvar);

    /** Атомарное запись в переменную под адресу pvar значения value. */
    inline static AtomicType32 Write(AtomicType32* pvar,
                                     const ArgType& value);

    /** Атомарное запись в переменную по адресу pvar значения value, в случае
      * если она равна cmp. В противном случае переменная не изменяется.
      * Возвращается старое значение. */
    inline static AtomicType32 CompareWrite(AtomicType32* pvar,
                                            const ArgType& newvalue,
                                            const ArgType& cmp);

    /** Атомарное инкрементирование переменной по адресу pvar.
      * Возвращается старое значение.
      * Таким образом реализуется семантира постинкремента.
      * Может оказаться быстрее, чем Add(v, 1). */
    inline static AtomicType32 Increment(AtomicType32* pvar);

    /** Атомарное декрементирование переменной по адресу pvar.
      * Возвращается старое значение
      * Таким образом реализуется семантира постдекремента.
      * Может оказаться быстрее, чем Sub(v, 1). */
    inline static AtomicType32 Decrement(AtomicType32* pvar);

    /** Атомарное прибавление к переменной по адресу pvar значения value.
      * Возвращается старое значение. */
    inline static AtomicType32 Add(AtomicType32* pvar,
                                   const ArgType& value);

    /** Атомарное вычитание из переменной по адресу pvar значения value.
      * Возвращается старое значение. */
    inline static AtomicType32 Sub(AtomicType32* pvar,
                                   const ArgType& value);

    /** Атомарное применение И к переменной по адресу pvar значением value.
      * Возвращается старое значение. */
    inline static AtomicType32 And(AtomicType32* pvar,
                                   const ArgType& value);

    /** Атомарное применение ИЛИ к переменной по адресу pvar значением value.
      * Возвращается старое значение. */
    inline static AtomicType32 Or(AtomicType32* pvar,
                                  const ArgType& value);

    /** Атомарное применение XOR к переменной по адресу pvar значением value.
      * Возвращается старое значение. */
    inline static AtomicType32 Xor(AtomicType32* pvar,
                                   const ArgType& value);
};

/** Реализация атомарного поведения переменной через перегрузку операторов.
  * Класс рекомендуется использовать в основном для сравнений
  * и инкрементации/декрементации переменной. Остальные операции нарушают
  * семантику атомарных функций из-за семантики операций в C++.
  * Например, операция operator+=() соответствует атомарной фукнции Add(),
  * но возвращаемое значение последней — копия значения переменной до сложения,
  * в то время как возвращаемое значение первой — ссылка на данный объект,
  * который в любой момент может измениться другим потоком. */
template<typename TAtomicType>
class Atomic
{
  private:
    typedef AtomicOp<TAtomicType> MyAtomicOp;

  public:
    typedef TAtomicType AtomicType;

    explicit Atomic(const TAtomicType v = 0) : v_(v) {}

    Atomic(const Atomic& rhv) : v_(rhv.Value()) {}

    ~Atomic() {}

    Atomic& operator=(const Atomic& rhv)
    {
        if ( this != &rhv )
        {
            MyAtomicOp::Write(
                &v_,
                static_cast<typename MyAtomicOp::ArgType>(rhv.Value()));
        }
        return *this;
    }

    Atomic& operator+=(const Atomic& rhv)
    {
        MyAtomicOp::Add(&v_, rhv.Value());
        return *this;
    }

    Atomic& operator-=(const Atomic& rhv)
    {
        MyAtomicOp::Sub(&v_, rhv.Value());
        return *this;
    }

    Atomic& operator&=(const Atomic& rhv)
    {
        MyAtomicOp::And(&v_, rhv.Value());
        return *this;
    }

    Atomic& operator|=(const Atomic& rhv)
    {
        MyAtomicOp::Or(&v_, rhv.Value());
        return *this;
    }

    Atomic& operator^=(const Atomic& rhv)
    {
        MyAtomicOp::Xor(&v_, rhv.Value());
        return *this;
    }

    const Atomic operator+(const Atomic& rhv)
    {
        return Atomic(*this) += rhv;
    }

    const Atomic operator-(const Atomic& rhv)
    {
        return Atomic(*this) -= rhv;
    }

    const Atomic operator&(const Atomic& rhv)
    {
        return Atomic(*this) &= rhv;
    }

    const Atomic operator|(const Atomic& rhv)
    {
        return Atomic(*this) |= rhv;
    }

    const Atomic operator^(const Atomic& rhv)
    {
        return Atomic(*this) ^= rhv;
    }

    // Операция имеет отличную от Increment семантику, последняя возвращает
    // копию значения, которое было перед выполнением операции, в то время
    // как данный оператор, следуя семантике C++, возвращает ссылку на текущий
    // объект, значение которой может измениться в другом потоке.
    Atomic& operator++()
    {
        MyAtomicOp::Increment(&v_);
        return *this;
    }

    // Постфиксная форма инкремента имеет семантику, аналогичную функции
    // атомарного инкремента: возвращаемое значение соответствует значению
    // переменной до инкремента.
    Atomic operator++(int)
    {
        // Нельзя реализовывать через Atomic& operator++(),
        // это изменит семантику, "убив" атомарность.
        return Atomic(MyAtomicOp::Increment(&v_));
    }

    Atomic& operator--()
    {
        MyAtomicOp::Decrement(&v_);
        return *this;
    }

    Atomic operator--(int)
    {
        return Atomic(MyAtomicOp::Decrement(&v_));
    }

    Atomic operator+() const
    {
        return Atomic(+Value());
    }

    Atomic operator-() const
    {
        return Atomic(-Value());
    }

    Atomic operator!() const
    {
        return Atomic(!Value());
    }

    Atomic operator~() const
    {
        return Atomic(~Value());
    }

    bool operator==(const Atomic& rhv) const
    {
        return Value() == rhv.Value();
    }

    bool operator!=(const Atomic& rhv) const
    {
        return Value() != rhv.Value();
    }

    bool operator>(const Atomic& rhv) const
    {
        return Value() > rhv.Value();
    }

    bool operator>=(const Atomic& rhv) const
    {
        return Value() >= rhv.Value();
    }

    bool operator<(const Atomic& rhv) const
    {
        return Value() < rhv.Value();
    }

    bool operator<=(const Atomic& rhv) const
    {
        return Value() <= rhv.Value();
    }

    AtomicType Value() const
    {
        return MyAtomicOp::Read(&v_);
    }

    AtomicType& Reference()
    {
        return v_;
    }

    const AtomicType& Reference() const
    {
        return v_;
    }


  private:
    AtomicType v_;
};


template<class TAtomicType>
inline ::std::ostream& operator<<(::std::ostream& os,
                                  const Atomic<TAtomicType>& a)
{
    return os << a.Value();
}

template<class TAtomicType>
inline ::std::istream& operator>>(::std::istream& is,
                                  Atomic<TAtomicType>& a)

{
    TAtomicType tmp;
    is >> tmp;
    if ( !is.fail() )
        a = Atomic<TAtomicType>(tmp);
    return is;
}






#ifdef _WIN32

AtomicType32 AtomicOp<AtomicType32>::Read(const AtomicType32* pvar)
{
    return *pvar;
}


AtomicType32 AtomicOp<AtomicType32>::Write(AtomicType32* pvar,
                                           const ArgType& value)
{
    // Помимо присваивания перед ним создает memory barrier.
    return InterlockedExchange(reinterpret_cast<volatile LONG*>(pvar), value);
}


AtomicType32 AtomicOp<AtomicType32>::CompareWrite(AtomicType32* pvar,
                                                  const ArgType& newvalue,
                                                  const ArgType& cmp)
{
    return InterlockedCompareExchange(reinterpret_cast<volatile LONG*>(pvar),
                                      newvalue, cmp);
}


AtomicType32 AtomicOp<AtomicType32>::Increment(AtomicType32* pvar)
{
    return InterlockedIncrement(reinterpret_cast<volatile LONG*>(pvar)) - 1;
}


AtomicType32 AtomicOp<AtomicType32>::Decrement(AtomicType32* pvar)
{
    return InterlockedDecrement(reinterpret_cast<volatile LONG*>(pvar)) + 1;
}


AtomicType32 AtomicOp<AtomicType32>::Add(AtomicType32* pvar,
                                         const ArgType& value)
{
    return InterlockedExchangeAdd(reinterpret_cast<volatile LONG*>(pvar),value);
}



AtomicType32 AtomicOp<AtomicType32>::Sub(AtomicType32* pvar,
                                         const ArgType& value)
{
    // Иначе warning о использовании rvalue в качестве lvalue.
    const int32_t neg_value = -value;
    return Add(pvar, neg_value);
}


AtomicType32 AtomicOp<AtomicType32>::And(AtomicType32* pvar,
                                         const ArgType& value)
{
    return _InterlockedAnd(reinterpret_cast<volatile LONG*>(pvar), value);
}


AtomicType32 AtomicOp<AtomicType32>::Or(AtomicType32* pvar,
                                        const ArgType& value)
{
    return _InterlockedOr(reinterpret_cast<volatile LONG*>(pvar), value);
}


AtomicType32 AtomicOp<AtomicType32>::Xor(AtomicType32* pvar,
                                         const ArgType& value)
{
    return _InterlockedXor(reinterpret_cast<volatile LONG*>(pvar), value);
}


#else

AtomicType32 AtomicOp<AtomicType32>::Read(const AtomicType32* pvar)
{
    return *pvar;
}


AtomicType32 AtomicOp<AtomicType32>::Write(AtomicType32* pvar,
                                           const int32_t &value)
{
    return __sync_lock_test_and_set(pvar, value);
}


AtomicType32 AtomicOp<AtomicType32>::CompareWrite(AtomicType32* pvar,
                                                  const ArgType& newvalue,
                                                  const ArgType& cmp)
{
    return __sync_val_compare_and_swap(pvar, cmp, newvalue);
}


AtomicType32 AtomicOp<AtomicType32>::Increment(AtomicType32* pvar)
{
    return __sync_fetch_and_add(pvar, 1);
}


AtomicType32 AtomicOp<AtomicType32>::Decrement(AtomicType32* pvar)
{
    return __sync_fetch_and_sub(pvar, 1);
}


AtomicType32 AtomicOp<AtomicType32>::Add(AtomicType32* pvar,
                                         const ArgType& value)
{
    return __sync_fetch_and_add(pvar, value);
}


AtomicType32 AtomicOp<AtomicType32>::Sub(AtomicType32* pvar,
                                         const ArgType& value)
{
    const int32_t neg_value = -value;
    return Add(pvar, neg_value);
}


AtomicType32 AtomicOp<AtomicType32>::And(AtomicType32* pvar,
                                         const ArgType& value)
{
    return __sync_fetch_and_and(pvar, value);
}


AtomicType32 AtomicOp<AtomicType32>::Or(AtomicType32* pvar,
                                        const ArgType& value)
{
    return __sync_fetch_and_or(pvar, value);
}


AtomicType32 AtomicOp<AtomicType32>::Xor(AtomicType32* pvar,
                                         const ArgType& value)
{
    return __sync_fetch_and_xor(pvar, value);
}

#endif  // _WIN32

}  // libv

#endif  // LIBV_ATOMIC_H_
